﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ViewModeSwitcher.cs" company="ScRePt">
//   Copyright © ScRePt 2010 - 2012
// </copyright>
// <summary>
//   The view mode switcher.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace MP3D.Zoom
{
    #region Using Directives

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Drawing;
    using System.Threading;

    using MediaPortal;
    using MediaPortal.GUI.Library;

    #endregion

    /// <summary>
    /// The view mode switcher.
    /// </summary>
    public class ViewModeSwitcher : IDisposable
    {
        #region Constants and Fields

        /// <summary>
        /// The analyzer.
        /// </summary>
        private readonly FrameAnalyzer analyzer = new FrameAnalyzer();

        /// <summary>
        /// The crop samples.
        /// </summary>
        private readonly Collection<Rectangle> cropSamples = new Collection<Rectangle>();

        /// <summary>
        /// The grabber.
        /// </summary>
        private readonly FrameGrabber grabber = FrameGrabber.GetInstance();

        /// <summary>
        /// The thread.
        /// </summary>
        private readonly BackgroundWorker thread = new BackgroundWorker();

        /// <summary>
        /// The crop rectangle.
        /// </summary>
        private Rectangle cropRectangle;

        /// <summary>
        /// The detection samples.
        /// </summary>
        private int detectionSamples;

        /// <summary>
        /// The should crop.
        /// </summary>
        private bool shouldCrop;

        /// <summary>
        /// The stop thread.
        /// </summary>
        private bool stopThread;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModeSwitcher"/> class.
        /// </summary>
        public ViewModeSwitcher()
        {
            this.thread.WorkerSupportsCancellation = true;
            this.thread.DoWork += new DoWorkEventHandler(this.ThreadProcess);
            this.thread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.OnThreadCompleted);
            this.thread.RunWorkerAsync();
        }

        #endregion

        #region Delegates

        /// <summary>
        /// The stereoscopic crop event handler.
        /// </summary>
        /// <param name="left">
        /// The left.
        /// </param>
        /// <param name="top">
        /// The top.
        /// </param>
        /// <param name="right">
        /// The right.
        /// </param>
        /// <param name="bottom">
        /// The bottom.
        /// </param>
        public delegate void StereoscopicCropEventHandler(int left, int top, int right, int bottom);

        #endregion

        #region Events

        /// <summary>
        /// The crop.
        /// </summary>
        public event StereoscopicCropEventHandler Crop;

        #endregion

        #region Implemented Interfaces

        #region IDisposable

        /// <summary>
        /// The dispose.
        /// </summary>
        public void Dispose()
        {
            this.stopThread = true;

            if (this.thread == null)
            {
                return;
            }

            this.thread.CancelAsync();
            int loopCounter = 0;

            while (this.thread.CancellationPending && loopCounter++ < 1000)
            {
                Thread.Sleep(1);
            }

            this.thread.Dispose();
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The detect crop.
        /// </summary>
        private void DetectCrop()
        {
            if (GUIGraphicsContext.RenderBlackImage)
            {
                this.detectionSamples--;
                return;
            }

            Bitmap frame = this.grabber.GetCurrentImage();

            if (frame == null)
            {
                this.detectionSamples--;
                return;
            }

            if (frame.Height == 0 || frame.Width == 0)
            {
                frame.Dispose();
                this.detectionSamples--;
                return;
            }

            Rectangle bounds = new Rectangle();

            if (!this.analyzer.FindBounds(frame, ref bounds))
            {
                this.detectionSamples--;
                frame.Dispose();
                return;
            }

            bounds.X--;
            bounds.Y--;

            if (bounds.X < 0)
            {
                bounds.X = 0;
            }

            if (bounds.Y < 0)
            {
                bounds.Y = 0;
            }

            bounds.Width = frame.Width - bounds.Width - bounds.X - 2;
            bounds.Height = frame.Height - bounds.Height - bounds.Y - 2;

            if (bounds.Width < 0)
            {
                bounds.Width = 0;
            }

            if (bounds.Height < 0)
            {
                bounds.Height = 0;
            }

            this.cropSamples.Add(bounds);
            frame.Dispose();
        }

        /// <summary>
        /// The on thread completed.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void OnThreadCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (this.Crop != null && this.shouldCrop)
            {
                this.Crop(this.cropRectangle.X, this.cropRectangle.Y, this.cropRectangle.Width, this.cropRectangle.Height);
            }
        }

        /// <summary>
        /// The stop thread if samples are reliable.
        /// </summary>
        private void StopThreadIfSamplesAreReliable()
        {
            if (this.detectionSamples < 10 || this.cropSamples.Count == 0)
            {
                return;
            }

            if (this.detectionSamples > 100)
            {
                this.stopThread = true;
                return;
            }

            // Get the median of the sampled values
            Rectangle medianOfSamples = new Rectangle();

            foreach (Rectangle sample in this.cropSamples)
            {
                medianOfSamples.X += sample.X;
                medianOfSamples.Y += sample.Y;
                medianOfSamples.Width += sample.Width;
                medianOfSamples.Height += sample.Height;
            }

            medianOfSamples.X /= this.cropSamples.Count;
            medianOfSamples.Y /= this.cropSamples.Count;
            medianOfSamples.Width /= this.cropSamples.Count;
            medianOfSamples.Height /= this.cropSamples.Count;

            // Get the sampled values variation
            Rectangle distanceFromMedian = new Rectangle();

            foreach (Rectangle sample in this.cropSamples)
            {
                distanceFromMedian.X += sample.X - medianOfSamples.X;
                distanceFromMedian.Y += sample.Y - medianOfSamples.Y;
                distanceFromMedian.Width += sample.Width - medianOfSamples.Width;
                distanceFromMedian.Height += sample.Height - medianOfSamples.Height;
            }

            if (Math.Abs(distanceFromMedian.X) / this.cropSamples.Count > 10)
            {
                return;
            }

            if (Math.Abs(distanceFromMedian.Y) / this.cropSamples.Count > 10)
            {
                return;
            }

            if (Math.Abs(distanceFromMedian.Width) / this.cropSamples.Count > 10)
            {
                return;
            }

            if (Math.Abs(distanceFromMedian.Height) / this.cropSamples.Count > 10)
            {
                return;
            }

            // Recalculate median without the extreme values
            Rectangle preciseMedian = new Rectangle();
            Rectangle newmedianCount = new Rectangle();

            foreach (Rectangle sample in this.cropSamples)
            {
                if (Math.Abs(sample.X - medianOfSamples.X) <= 10)
                {
                    newmedianCount.X++;
                    preciseMedian.X += sample.X;
                }

                if (Math.Abs(sample.Y - medianOfSamples.Y) <= 10)
                {
                    newmedianCount.Y++;
                    preciseMedian.Y += sample.Y;
                }

                if (Math.Abs(sample.Width - medianOfSamples.Width) <= 10)
                {
                    newmedianCount.Width++;
                    preciseMedian.Width += sample.Width;
                }

                if (Math.Abs(sample.Height - medianOfSamples.Height) <= 10)
                {
                    newmedianCount.Height++;
                    preciseMedian.Height += sample.Height;
                }
            }

            if (newmedianCount.X == 0 || newmedianCount.Y == 0 || newmedianCount.Width == 0 || newmedianCount.Height == 0)
            {
                return;
            }

            preciseMedian.X /= newmedianCount.X;
            preciseMedian.Y /= newmedianCount.Y;
            preciseMedian.Width /= newmedianCount.Width;
            preciseMedian.Height /= newmedianCount.Height;

            // Apply crop
            this.cropRectangle = preciseMedian;
            this.shouldCrop = true;
            this.stopThread = true;
        }

        /// <summary>
        /// The thread process.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void ThreadProcess(object sender, DoWorkEventArgs e)
        {
            while (Thread.CurrentThread.ThreadState == ThreadState.Background && !this.stopThread)
            {
                this.detectionSamples++;
                this.DetectCrop();
                this.StopThreadIfSamplesAreReliable();
                Thread.Sleep(100);
            }

            this.stopThread = false;
            return;
        }

        #endregion
    }
}